package vg.services.user_interface_manager.additional_swing_components;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;

/**
 * This class realizes comboBox for values of conditional panel.
 * All methods of this class by need called from EDT only!!!
 * @author tzolotuhin
 */
public class ValueComboBox {
	private final static long serialVersionUID = 1L; 
	// Data
	private final List<ComboBoxItem> data;
	// Mutex
	private final Object theObjectMutex;
	// Components
	private final JComboBox view;
	private final InitialSelection initialSelection;
	/**
	 * Constructor without parameters. Creates empty comboBox.
	 */
	public ValueComboBox() {
		this(null);
	}
	/**
	 * Constructor.
	 * @param items - list of elements for comboBox.
	 */
	public ValueComboBox(final String[] items) {
		// Initialize mutex
		this.theObjectMutex = new Object();
		// Initialize data
		this.data = new ArrayList<ComboBoxItem>();
		// Initialize graphical components.
		this.view = new JComboBox();
		this.view.setRenderer(new ComboBoxRenderer());
		this.view.setEditable(true);
		
		addItems(items);
		
		this.initialSelection = new InitialSelection(this.view);
	}
	/**
	 * This method returns selected item.
	 */
	public String getSelectedItem() {
		synchronized (theObjectMutex) {
			Object obj = this.view.getSelectedItem();
			if(obj != null) {
				if(obj instanceof ComboBoxItem) {
					ComboBoxItem cbItem = (ComboBoxItem)obj;
					return cbItem.data;
				} else {
					return obj.toString();
				}
			}
			return null;
		}
	}
	public JComponent getView() {
		return this.view;
	}
	public void removeAllItems() {
		synchronized (theObjectMutex) {
			this.data.clear();
			this.view.removeAllItems();
		}
	}
	public void setShowPopupDuringKeyPressed(boolean state) {
		synchronized (theObjectMutex) {
			this.initialSelection.setShowPopupDuringKeyPressed(state);
		}
	}
	public void addKeyListener(KeyAdapter keyAdapter) {
		synchronized (theObjectMutex) {
			((JTextField)this.view.getEditor().getEditorComponent()).addKeyListener(keyAdapter);
		}
	}
	public void removeKeyListener(KeyAdapter keyAdapter) {
		synchronized (theObjectMutex) {
			((JTextField)this.view.getEditor().getEditorComponent()).removeKeyListener(keyAdapter);
		}
	}
	public void addActionListener(ActionListener l) {
		synchronized (theObjectMutex) {
			this.view.addActionListener(l);
		}
	}
	public void removeActionListener(ActionListener l) {
		synchronized (theObjectMutex) {
			this.view.removeActionListener(l);
		}		
	}
	/**
	 * Adds new item in comboBox.
	 * Item not be null. And item shouldn't contains in comboBox, already.
	 * If it is, the item will not joined to comboBox.
	 */
	public void addItem(String item) {
		if(item == null) return;
		synchronized (theObjectMutex) {
			ComboBoxItem cbItem = new ComboBoxItem(item);
			if(this.data.contains(cbItem)) return;
			this.data.add(cbItem);
			this.view.addItem(cbItem);
		}
	}
	/**
	 * Adds new array of items.
	 * Array of items and an item not be null. And an item shouldn't contains in comboBox, already.
	 * If it is, the item will not joined to comboBox.
	 */
	public void addItems(List<String> items) {
		if(items == null) return;
		synchronized (theObjectMutex) {
			for(String buf : items) {
				if(buf == null) continue;
				ComboBoxItem newCBI = new ComboBoxItem(buf);
				if(this.data.contains(newCBI)) continue;
				this.data.add(newCBI);
				this.view.addItem(newCBI);
			}
		}
	}
	/**
	 * Adds new array of items.
	 * Array of items and an item not be null. And an item shouldn't contains in comboBox, already.
	 * If it is, the item will not joined to comboBox.
	 */
	public void addItems(String[] items) {
		if(items == null) return;
		synchronized (theObjectMutex) {
			for(String buf : items) {
				if(buf == null) continue;
				ComboBoxItem newCBI = new ComboBoxItem(buf);
				if(this.data.contains(newCBI)) continue;
				this.data.add(newCBI);
				this.view.addItem(newCBI);
			}
		}
	}
	/**
	 * This method returns true, if real list contains obj, otherwise false.
	 */
	public boolean isContains(Object obj) {
		synchronized (theObjectMutex) {
			for(ComboBoxItem buf : this.data) {
				if(buf.data != null && buf.data.equals(obj)) {
					return true;
				}
			}
		}
		return false;
	}
	public void selectObject(String obj) {
		synchronized (theObjectMutex) {
			ComboBoxItem re = null;
			// update visibility of items
			for(ComboBoxItem buf : this.data) {
				if(buf.data != null && buf.data.equals(obj)) {
					re = buf;
				}
			}
			this.view.setSelectedItem(re);
		}
	}
	public void searchReset() {
		synchronized (theObjectMutex) {
			this.view.setSelectedItem("");
		}
	}
	public void sortItems() {
		String[] a = new String[this.data.size()];
		a = this.data.toArray(a);
		Arrays.sort(a);
		//TODO
	}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// PRIVATE CLASSES
///////////////////////////////////////////////////////////////////////////////
	private static class ComboBoxItem {
		public final String data;
		public ComboBoxItem(String data) {
			this.data = data;
		}
		@Override
		public String toString() {
			return data.toString();
		}
		@Override
		public boolean equals(Object obj) {
			if(obj == null) return false;
			if(obj instanceof ComboBoxItem) {
				String objData = ((ComboBoxItem) obj).data;
				if(objData!= null && objData.equals(data)) {
					return true;
				}
			}
			return false;
		}
		public String smartToString() {
			if(data.length() > 20) {
				return data.substring(0, 20) + "...";
			}
			return data.toString();
		}
	}

	private static class InitialSelection extends PlainDocument {
		private final static long serialVersionUID = 1L;
		// Graphical components
		private final ComboBoxModel model;
		private final JTextComponent editor;
		private boolean popupDuringKeyPressed = true;
		// flag to indicate if setSelectedItem has been called
		// subsequent calls to remove/insertString should be ignored
		private boolean selecting = false;
		/**
		 * Constructor.
		 */
		public InitialSelection(final JComboBox comboBox) {
			this.model = comboBox.getModel();
			this.editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
			this.editor.setDocument(this);
			comboBox.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if (!selecting) {
						highlightCompletedText(0);
					}
				}
			});
			this.editor.addKeyListener(new KeyAdapter() {
				public void keyPressed(KeyEvent e) {
					if (comboBox.isDisplayable() && popupDuringKeyPressed)
						comboBox.setPopupVisible(true);
				}
			});
			// Handle initially selected object
			Object selected = comboBox.getSelectedItem();
			if (selected != null)
				setText(selected.toString());
			highlightCompletedText(0);
		}
		public void setShowPopupDuringKeyPressed(boolean state) {
			this.popupDuringKeyPressed = state;
		}
		public void remove(int offs, int len) throws BadLocationException {
			// return immediately when selecting an item
			if (this.selecting)
				return;
			super.remove(offs, len);
		}

		public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
			// return immediately when selecting an item
			if (this.selecting)
				return;
			// insert the string into the document
			super.insertString(offs, str, a);
			// lookup and select a matching item
			Object item = lookupItem(getText(0, getLength()));
			if (item != null) {
				setSelectedItem(item);
				setText(item.toString());
				// select the completed part
				highlightCompletedText(offs + str.length());
			}		
		}

		private void setText(String text) {
			try {
				// remove all text and insert the completed string
				super.remove(0, getLength());
				super.insertString(0, text, null);
			} catch (BadLocationException e) {
				throw new RuntimeException(e.toString());
			}
		}

		private void highlightCompletedText(int start) {
			this.editor.setCaretPosition(getLength());
			this.editor.moveCaretPosition(start);
		}

		private void setSelectedItem(Object item) {
			this.selecting = true;
			this.model.setSelectedItem(item);
			this.selecting = false;
		}

		private Object lookupItem(String pattern) {
			Object selectedItem = model.getSelectedItem();
			// only search for a different item if the currently selected does not
			// match
			if (selectedItem != null && startsWithIgnoreCase(selectedItem.toString(), pattern)) {
				return selectedItem;
			} else {
				// iterate over all items
				for (int i = 0, n = model.getSize(); i < n; i++) {
					Object currentItem = model.getElementAt(i);
					// current item starts with the pattern?
					if (startsWithIgnoreCase(currentItem.toString(), pattern)) {
						return currentItem;
					}
				}
			}
			// no item starts with the pattern => return null
			return null;
		}

		// checks if str1 starts with str2 - ignores case
		private boolean startsWithIgnoreCase(String str1, String str2) {
			return str1.toUpperCase().startsWith(str2.toUpperCase());
		}
	}

	private static class ComboBoxRenderer extends BasicComboBoxRenderer {
		private static final long serialVersionUID = 1L;

		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
			if (isSelected) {
				setBackground(list.getSelectionBackground());
				setForeground(list.getSelectionForeground());
				if (-1 < index && value != null && value instanceof ComboBoxItem) {
					ComboBoxItem cbItem = (ComboBoxItem) value;
					list.setToolTipText(cbItem.toString());
				}
			} else {
				setBackground(list.getBackground());
				setForeground(list.getForeground());
			}
			setFont(list.getFont());
			if(value == null) {
				setText("");
			} else if(value instanceof ComboBoxItem) {
				setText(((ComboBoxItem) value).smartToString());
			} else {
				setText(value.toString());
			}
			return this;
		}
	}

}
